#include "SingleFloatingLeg.h"
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/cashflows/iborcoupon.hpp>
#include <ql/cashflows/cashflowvectors.hpp>
#include <ql/cashflows/cashflows.hpp>
#include <ql/cashflows/couponpricer.hpp>
#include <ql/indexes/iborindex.hpp>
#include <ql/termstructures/yieldtermstructure.hpp>

namespace QuantLib {

    SingleFloatingLeg::SingleFloatingLeg(
                     Type type,
                     Real nominal,
					 const Schedule& floatingSchedule,
					 const boost::shared_ptr<IborIndex>& iborIndex,
                     Spread spread,
                     const DayCounter& floatingDayCount)
					 :Swap(1), 
						type_(type), 
						nominal_(nominal),
						floatingSchedule_(floatingSchedule), 
						iborIndex_(iborIndex), 
						spread_(spread)
						{
							paymentConvention_ = floatingSchedule_.businessDayConvention();
					
							legs_[0] = IborLeg(floatingSchedule_, iborIndex_)
								.withNotionals(nominal_)
								.withPaymentDayCounter(floatingDayCount_)
								.withPaymentAdjustment(paymentConvention_)
								.withSpreads(spread_);

							for (Leg::const_iterator i = legs_[0].begin(); i < legs_[0].end(); ++i)
								registerWith(*i);

							switch (type_) {
							  case Payer:
								payer_[0] = -1.0;
								break;
							  case Receiver:
								payer_[0] = +1.0;
								break;
							  default:
								QL_FAIL("Unknown floating-leg type");
						}
    }

    void SingleFloatingLeg::setupArguments(PricingEngine::arguments* args) const {

        Swap::setupArguments(args);

        SingleFloatingLeg::arguments* arguments =
            dynamic_cast<SingleFloatingLeg::arguments*>(args);

        if (!arguments)  // it's a swap engine...
            return;

        arguments->type = type_;
        arguments->nominal = nominal_;

        const Leg& floatingCoupons = floatingLeg();

        arguments->floatingResetDates = arguments->floatingPayDates =
            arguments->floatingFixingDates =
            std::vector<Date>(floatingCoupons.size());
        arguments->floatingAccrualTimes =
            std::vector<Time>(floatingCoupons.size());
        arguments->floatingSpreads =
            std::vector<Spread>(floatingCoupons.size());
        arguments->floatingCoupons = std::vector<Real>(floatingCoupons.size());
        for (Size i=0; i<floatingCoupons.size(); ++i) {
            boost::shared_ptr<IborCoupon> coupon =
            boost::dynamic_pointer_cast<IborCoupon>(floatingCoupons[i]);

            arguments->floatingResetDates[i] = coupon->accrualStartDate();
            arguments->floatingPayDates[i] = coupon->date();

            arguments->floatingFixingDates[i] = coupon->fixingDate();
            arguments->floatingAccrualTimes[i] = coupon->accrualPeriod();
            arguments->floatingSpreads[i] = coupon->spread();
            try {
                arguments->floatingCoupons[i] = coupon->amount();
            } catch (Error&) {
                arguments->floatingCoupons[i] = Null<Real>();
            }
        }

    }

    Spread SingleFloatingLeg::fairSpread() const {
        calculate();
        QL_REQUIRE(fairSpread_ != Null<Spread>(), "result not available");
        return fairSpread_;
    }

    Real SingleFloatingLeg::floatingLegBPS() const {
        calculate();
        QL_REQUIRE(legBPS_[0] != Null<Real>(), "result not available");
        return legBPS_[0];
    }

    Real SingleFloatingLeg::floatingLegNPV() const {
        calculate();
        QL_REQUIRE(legNPV_[0] != Null<Real>(), "result not available");
        return legNPV_[0];
    }

    void SingleFloatingLeg::setupExpired() const {
        Swap::setupExpired();
        legBPS_[0] = 0.0;
        fairSpread_ = Null<Spread>();
    }

    void SingleFloatingLeg::fetchResults(const PricingEngine::results* r) const {
        static const Spread basisPoint = 1.0e-4;

        Swap::fetchResults(r);

        const SingleFloatingLeg::results* results =
            dynamic_cast<const SingleFloatingLeg::results*>(r);
        if (results) { // might be a swap engine, so no error is thrown
			fairSpread_ = results->fairSpread;
        } 
		else {
            fairSpread_ = Null<Spread>();
        }

        if (fairSpread_ == Null<Spread>()) {
            // ditto
            if (legBPS_[0] != Null<Real>())
                fairSpread_ = spread_ - NPV_/(legBPS_[0]/basisPoint);
        }
    }

    void SingleFloatingLeg::arguments::validate() const {
        Swap::arguments::validate();
        QL_REQUIRE(nominal != Null<Real>(), "nominal null or not set");
        QL_REQUIRE(floatingResetDates.size() == floatingPayDates.size(),
                   "number of floatinging start dates different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingFixingDates.size() == floatingPayDates.size(),
                   "number of floatinging fixing dates different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingAccrualTimes.size() == floatingPayDates.size(),
                   "number of floatinging accrual Times different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingSpreads.size() == floatingPayDates.size(),
                   "number of floatinging spreads different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingPayDates.size() == floatingCoupons.size(),
                   "number of floatinging payment dates different from "
                   "number of floatinging coupon amounts");

    }

    void SingleFloatingLeg::results::reset() {
        Swap::results::reset();
        fairSpread = Null<Spread>();
    }

    std::ostream& operator<<(std::ostream& out,
                             SingleFloatingLeg::Type t) {
        switch (t) {
          case SingleFloatingLeg::Payer:
            return out << "Payer";
          case SingleFloatingLeg::Receiver:
            return out << "Receiver";
          default:
            QL_FAIL("unknown SingleFloatingLeg::Type(" << Integer(t) << ")");
        }
    }

}
